from __future__ import annotations

import uuid
from collections.abc import Sequence
from unittest.mock import patch

from sentry.eventstore.models import GroupEvent
from sentry.issues.grouptype import PerformanceNPlusOneGroupType, ProfileFileIOGroupType
from sentry.models.group import Group
from sentry.testutils.cases import PerformanceIssueTestCase, SnubaTestCase, TestCase
from sentry.testutils.helpers.datetime import before_now, iso_format
from sentry.utils.samples import load_data
from tests.sentry.issues.test_utils import OccurrenceTestMixin


def _get_recommended_non_null(g: Group) -> GroupEvent:
    ret = g.get_recommended_event_for_environments()
    assert ret is not None
    return ret


def _get_latest_non_null(g: Group, environments: Sequence[str] = ()) -> GroupEvent:
    ret = g.get_latest_event_for_environments(environments)
    assert ret is not None
    return ret


def _get_oldest_non_null(g: Group, environments: Sequence[str] = ()) -> GroupEvent:
    ret = g.get_oldest_event_for_environments(environments)
    assert ret is not None
    return ret


class GroupTestSnuba(TestCase, SnubaTestCase, PerformanceIssueTestCase, OccurrenceTestMixin):
    def test_get_oldest_latest_for_environments(self):
        project = self.create_project()

        min_ago = iso_format(before_now(minutes=1))

        self.store_event(
            data={
                "event_id": "a" * 32,
                "environment": "production",
                "timestamp": min_ago,
                "fingerprint": ["group-1"],
            },
            project_id=project.id,
        )
        self.store_event(
            data={
                "event_id": "b" * 32,
                "environment": "production",
                "timestamp": min_ago,
                "fingerprint": ["group-1"],
            },
            project_id=project.id,
        )
        self.store_event(
            data={"event_id": "c" * 32, "timestamp": min_ago, "fingerprint": ["group-1"]},
            project_id=project.id,
        )

        group = Group.objects.get()

        assert _get_latest_non_null(group).event_id == "c" * 32
        assert group.get_latest_event_for_environments(["staging"]) is None
        assert _get_latest_non_null(group, ["production"]).event_id == "b" * 32
        assert _get_oldest_non_null(group).event_id == "a" * 32
        assert _get_oldest_non_null(group, ["staging", "production"]).event_id == "a" * 32
        assert group.get_oldest_event_for_environments(["staging"]) is None

    def test_perf_issue(self):
        group_fingerprint = f"{PerformanceNPlusOneGroupType.type_id}-group1"

        event_data_1 = load_data("transaction-n-plus-one", fingerprint=[group_fingerprint])
        event_data_1["timestamp"] = iso_format(before_now(seconds=10))
        event_data_1["start_timestamp"] = iso_format(before_now(seconds=11))
        event_data_1["event_id"] = "d" * 32
        event_data_2 = load_data("transaction-n-plus-one", fingerprint=[group_fingerprint])
        event_data_2["timestamp"] = iso_format(before_now(seconds=20))
        event_data_2["start_timestamp"] = iso_format(before_now(seconds=21))
        event_data_2["event_id"] = "f" * 32
        event_data_3 = load_data("transaction-n-plus-one", fingerprint=[group_fingerprint])
        event_data_3["timestamp"] = iso_format(before_now(seconds=30))
        event_data_3["start_timestamp"] = iso_format(before_now(seconds=31))
        event_data_3["event_id"] = "f" * 32

        transaction_event_1 = self.create_performance_issue(
            event_data=event_data_1, fingerprint=group_fingerprint
        )
        self.create_performance_issue(event_data=event_data_2, fingerprint=group_fingerprint)
        self.create_performance_issue(event_data=event_data_3, fingerprint=group_fingerprint)

        perf_group = transaction_event_1.group

        assert _get_latest_non_null(perf_group).event_id == "d" * 32
        assert _get_oldest_non_null(perf_group).event_id == "f" * 32

    def test_error_issue_get_helpful_for_environments(self):
        project = self.create_project()
        min_ago = iso_format(before_now(minutes=1))
        replay_id = uuid.uuid4().hex

        event_all_helpful_params = self.store_event(
            data={
                "event_id": "a" * 32,
                "timestamp": min_ago,
                "fingerprint": ["group-1"],
                "contexts": {
                    "replay": {"replay_id": replay_id},
                    "trace": {
                        "sampled": True,
                        "span_id": "babaae0d4b7512d9",
                        "trace_id": "a7d67cf796774551a95be6543cacd459",
                    },
                },
                "errors": [],
            },
            project_id=project.id,
            assert_no_errors=False,
        )
        self.store_event(
            data={
                "event_id": "b" * 32,
                "timestamp": min_ago,
                "fingerprint": ["group-1"],
                "contexts": {
                    "replay": {"replay_id": replay_id},
                },
                "errors": [{"type": "one"}, {"type": "two"}],
            },
            project_id=project.id,
            assert_no_errors=False,
        )
        event_none_helpful_params = self.store_event(
            data={"event_id": "c" * 32, "timestamp": min_ago, "fingerprint": ["group-1"]},
            project_id=project.id,
        )

        group = Group.objects.get()
        assert _get_recommended_non_null(group).event_id == event_all_helpful_params.event_id
        assert _get_latest_non_null(group).event_id == event_none_helpful_params.event_id
        assert _get_oldest_non_null(group).event_id == event_all_helpful_params.event_id

    def test_perf_issue_helpful(self):
        group_fingerprint = f"{PerformanceNPlusOneGroupType.type_id}-group1"

        transaction_event_data_with_all_helpful = load_data(
            "transaction-n-plus-one", fingerprint=[group_fingerprint]
        )
        transaction_event_data_with_all_helpful["timestamp"] = iso_format(before_now(seconds=10))
        transaction_event_data_with_all_helpful["start_timestamp"] = iso_format(
            before_now(seconds=11)
        )
        transaction_event_data_with_all_helpful["event_id"] = "d" * 32
        transaction_event_data_with_all_helpful["contexts"].update(
            {"profile": {"profile_id": uuid.uuid4().hex}}
        )
        transaction_event_data_with_all_helpful["contexts"].update(
            {"replay": {"replay_id": uuid.uuid4().hex}}
        )
        transaction_event_data_with_all_helpful["contexts"]["trace"]["sampled"] = True
        transaction_event_data_with_all_helpful["errors"] = []

        transaction_event_data_with_none_helpful = load_data(
            "transaction-n-plus-one", fingerprint=[group_fingerprint]
        )
        transaction_event_data_with_none_helpful["timestamp"] = iso_format(before_now(seconds=20))
        transaction_event_data_with_none_helpful["start_timestamp"] = iso_format(
            before_now(seconds=21)
        )
        transaction_event_data_with_none_helpful["event_id"] = "f" * 32

        transaction_event_1 = self.create_performance_issue(
            event_data=transaction_event_data_with_all_helpful, fingerprint=group_fingerprint
        )
        transaction_event_2 = self.create_performance_issue(
            event_data=transaction_event_data_with_none_helpful, fingerprint=group_fingerprint
        )

        perf_group = transaction_event_1.group

        assert _get_recommended_non_null(perf_group).event_id == transaction_event_1.event_id
        assert _get_latest_non_null(perf_group).event_id == transaction_event_1.event_id
        assert _get_oldest_non_null(perf_group).event_id == transaction_event_2.event_id

    def test_profile_issue_helpful(self):
        event_id_1 = uuid.uuid4().hex
        occurrence, _ = self.process_occurrence(
            event_id=event_id_1,
            project_id=self.project.id,
            event_data={
                "fingerprint": ["group-1"],
                "timestamp": before_now(minutes=2).isoformat(),
                "contexts": {
                    "profile": {"profile_id": uuid.uuid4().hex},
                    "replay": {"replay_id": uuid.uuid4().hex},
                    "trace": {
                        "sampled": True,
                        "span_id": "babaae0d4b7512d9",
                        "trace_id": "a7d67cf796774551a95be6543cacd459",
                    },
                },
                "errors": [],
            },
        )

        event_id_2 = uuid.uuid4().hex
        occurrence_2, _ = self.process_occurrence(
            event_id=event_id_2,
            project_id=self.project.id,
            event_data={
                "fingerprint": ["group-1"],
                "timestamp": before_now(minutes=1).isoformat(),
            },
        )

        group = Group.objects.get()
        group.update(type=ProfileFileIOGroupType.type_id)

        group_event = _get_recommended_non_null(group)
        assert group_event.event_id == occurrence.event_id
        self.assert_occurrences_identical(group_event.occurrence, occurrence)

        assert _get_latest_non_null(group).event_id == occurrence_2.event_id
        assert _get_oldest_non_null(group).event_id == occurrence.event_id

    @patch("sentry.quotas.backend.get_event_retention")
    def test_get_recommended_event_for_environments_retention_limit(self, mock_get_event_retention):
        """
        If last_seen is outside of the retention limit, falls back to the latest event behavior.
        """
        mock_get_event_retention.return_value = 90
        project = self.create_project()
        outside_retention_date = before_now(days=91)

        event = self.store_event(
            data={
                "event_id": "a" * 32,
                "timestamp": iso_format(outside_retention_date),
                "fingerprint": ["group-1"],
                "contexts": {},
                "errors": [],
            },
            project_id=project.id,
            assert_no_errors=False,
        )

        group = Group.objects.get()
        group.last_seen = before_now(days=91)
        assert _get_recommended_non_null(group).event_id == event.event_id
